use crate::{
    game::Game,
    geometry::Dir,
    moment::{Moment, Until},
};
use clap::{Args, Parser};
use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind};
use smol::{
    future::{zip, FutureExt},
    prelude::*,
};
use std::{
    fs::File,
    future::ready,
    io::{BufReader, BufWriter},
    pin::pin,
};

/// 2048 的 TUI 界面
///
/// 方向键以滑动。Esc 以退出。u 键以撤销。r 键以开始新游戏。h 键以获取提示。
#[derive(Parser)]
#[command(version, author)]
struct Opt {
    /// 新游戏
    #[command(flatten)]
    new: New,

    /// 从文件中读取存档
    #[arg(short, long, conflicts_with = "New")]
    load: Option<String>,

    /// 智能存档路径
    ///
    /// 保存、读取存档，若结束就开始新游戏
    #[arg(short, long, conflicts_with_all = ["save", "load"])]
    file: Option<String>,

    /// 历史上限
    ///
    /// 最多保存多少次操作记录，默认无限
    #[arg(short, long)]
    max_history: Option<usize>,

    /// 存档到文件
    ///
    /// 退出游戏时进行存档
    #[arg(short, long)]
    save: Option<String>,
}

#[derive(Args)]
struct New {
    /// 格子列数
    #[arg(short = 'W', long, default_value_t = 4)]
    width: usize,
    /// 格子行数
    #[arg(short = 'H', long, default_value_t = 4)]
    height: usize,
    /// 确定性生成
    ///
    /// 提供种子，令生成的块只取决于局面与种子
    #[arg(short, long)]
    deterministic: Option<u128>,
}

enum Action {
    Swipe(Dir),
    Undo,
    Quit,
    Reset,
    Hint,
}

fn channel() -> (oneshot::Sender<()>, impl Moment) {
    let (s, r) = oneshot::channel();
    let r = Until::new(async { r.await.expect("接收失败") });
    (s, r)
}

fn input() -> impl Stream<Item = Action> {
    EventStream::new()
        .filter_map(|e| match e.expect("读取事件错误") {
            Event::Key(e) => Some(e),
            _ => None,
        })
        .filter(|e| KeyEventKind::Press == e.kind)
        .filter_map(|e| {
            use Action::*;
            use KeyCode::*;
            Some(match e.code {
                Up => Swipe(Dir::Up),
                Down => Swipe(Dir::Down),
                Left => Swipe(Dir::Left),
                Right => Swipe(Dir::Right),
                Char('u') => Undo,
                Char('r') => Reset,
                Char('h') => Hint,
                Esc => Quit,
                _ => return None,
            })
        })
}

fn load_game<'a>(path: &str) -> Option<Game<'a>> {
    let file = File::open(path).ok()?;
    serde_json::from_reader(BufReader::new(file)).ok()
}

impl Opt {
    fn game<'a>(&self) -> Game<'a> {
        let mut game = if let Some(ref path) = self.file {
            load_game(path)
                .filter(Game::cont)
                .unwrap_or_else(|| self.new_game())
        } else if let Some(ref path) = self.load {
            load_game(path).expect("读取存档错误")
        } else {
            self.new_game()
        };

        if let Some(max_history) = self.max_history {
            game.set_max_history(max_history);
        }

        game
    }

    fn new_game<'a>(&self) -> Game<'a> {
        Game::new(
            (self.new.width, self.new.height).into(),
            self.new.deterministic,
        )
        .expect("尺寸错误")
    }

    fn save_path(&self) -> Option<&str> {
        self.file.as_deref().or(self.save.as_deref())
    }
}

pub async fn run() {
    let opt = Opt::parse();
    let mut game = opt.game();

    let (mut stop_s, stop_r) = channel();
    let mut stop_r = pin!(stop_r);

    let mut anim = game.init(stop_r.as_mut()).boxed_local();
    let mut input = input();

    loop {
        let input = async {
            let dir = input.next().await.expect("没有事件");
            stop_s.send(()).expect("发送失败");
            dir
        };
        let action = zip(input, anim).await.0;

        let (s, r) = channel();
        stop_s = s;
        stop_r.set(r);
        let stop = stop_r.as_mut();

        anim = match action {
            Action::Swipe(dir) => Some(game.swipe(dir, stop).boxed_local()),
            Action::Undo => game.undo(stop).map(FutureExt::boxed_local),
            Action::Quit => break,
            Action::Reset => {
                drop(game);
                game = opt.game();
                Some(game.init(stop).boxed_local())
            }
            Action::Hint => Some(game.hint(stop).boxed_local()),
        }
        .unwrap_or_else(|| ready(()).boxed_local());
    }

    if let Some(path) = opt.save_path() {
        let file = File::create(path).expect("打开文件错误");
        serde_json::to_writer(BufWriter::new(file), &game).expect("存档写入错误");
    }
}
